//
// Created by Administrator on 2022/11/14 0014.
//

#include <onix/interrupt.h>
#include <onix/assert.h>
#include <onix/fifo.h>
#include <onix/mutex.h>
#include <onix/task.h>
#include <onix/io.h>
#include <onix/device.h>
#include <onix/keyboard.h>

#define KEYBOARD_DATA_PORT  0x60
#define KEYBOARD_CTRL_PORT  0x64

#define KEYBOARD_CMD_LED 0xED // 设置 LED 状态
#define KEYBOARD_CMD_ACK 0xFA // ACK

#define INV 0 // 不可见字符

#define CODE_PRINT_SCREEN_DOWN 0xB7

typedef enum
{
    KEY_NONE,
    KEY_ESC,
    KEY_1,
    KEY_2,
    KEY_3,
    KEY_4,
    KEY_5,
    KEY_6,
    KEY_7,
    KEY_8,
    KEY_9,
    KEY_0,
    KEY_MINUS,
    KEY_EQUAL,
    KEY_BACKSPACE,
    KEY_TAB,
    KEY_Q,
    KEY_W,
    KEY_E,
    KEY_R,
    KEY_T,
    KEY_Y,
    KEY_U,
    KEY_I,
    KEY_O,
    KEY_P,
    KEY_BRACKET_L,
    KEY_BRACKET_R,
    KEY_ENTER,
    KEY_CTRL_L,
    KEY_A,
    KEY_S,
    KEY_D,
    KEY_F,
    KEY_G,
    KEY_H,
    KEY_J,
    KEY_K,
    KEY_L,
    KEY_SEMICOLON,
    KEY_QUOTE,
    KEY_BACKQUOTE,
    KEY_SHIFT_L,
    KEY_BACKSLASH,
    KEY_Z,
    KEY_X,
    KEY_C,
    KEY_V,
    KEY_B,
    KEY_N,
    KEY_M,
    KEY_COMMA,
    KEY_POINT,
    KEY_SLASH,
    KEY_SHIFT_R,
    KEY_STAR,
    KEY_ALT_L,
    KEY_SPACE,
    KEY_CAPSLOCK,
    KEY_F1,
    KEY_F2,
    KEY_F3,
    KEY_F4,
    KEY_F5,
    KEY_F6,
    KEY_F7,
    KEY_F8,
    KEY_F9,
    KEY_F10,
    KEY_NUMLOCK,
    KEY_SCRLOCK,
    KEY_PAD_7,
    KEY_PAD_8,
    KEY_PAD_9,
    KEY_PAD_MINUS,
    KEY_PAD_4,
    KEY_PAD_5,
    KEY_PAD_6,
    KEY_PAD_PLUS,
    KEY_PAD_1,
    KEY_PAD_2,
    KEY_PAD_3,
    KEY_PAD_0,
    KEY_PAD_POINT,
    KEY_54,
    KEY_55,
    KEY_56,
    KEY_F11,
    KEY_F12,
    KEY_59,
    KEY_WIN_L,
    KEY_WIN_R,
    KEY_CLIPBOARD,
    KEY_5D,
    KEY_5E,

    // 以下为自定义按键，为和 keymap 索引匹配
    KEY_PRINT_SCREEN,
} KEY;

static char keymap[][4] = {
        /* 扫描码 未与 shift 组合  与 shift 组合 以及相关状态 */
        /* ---------------------------------- */
        /* 0x00 */ {INV, INV, FALSE, FALSE},   // NULL
        /* 0x01 */ {0x1b, 0x1b, FALSE, FALSE}, // ESC
        /* 0x02 */ {'1', '!', FALSE, FALSE},
        /* 0x03 */ {'2', '@', FALSE, FALSE},
        /* 0x04 */ {'3', '#', FALSE, FALSE},
        /* 0x05 */ {'4', '$', FALSE, FALSE},
        /* 0x06 */ {'5', '%', FALSE, FALSE},
        /* 0x07 */ {'6', '^', FALSE, FALSE},
        /* 0x08 */ {'7', '&', FALSE, FALSE},
        /* 0x09 */ {'8', '*', FALSE, FALSE},
        /* 0x0A */ {'9', '(', FALSE, FALSE},
        /* 0x0B */ {'0', ')', FALSE, FALSE},
        /* 0x0C */ {'-', '_', FALSE, FALSE},
        /* 0x0D */ {'=', '+', FALSE, FALSE},
        /* 0x0E */ {'\b', '\b', FALSE, FALSE}, // BACKSPACE
        /* 0x0F */ {'\t', '\t', FALSE, FALSE}, // TAB
        /* 0x10 */ {'q', 'Q', FALSE, FALSE},
        /* 0x11 */ {'w', 'W', FALSE, FALSE},
        /* 0x12 */ {'e', 'E', FALSE, FALSE},
        /* 0x13 */ {'r', 'R', FALSE, FALSE},
        /* 0x14 */ {'t', 'T', FALSE, FALSE},
        /* 0x15 */ {'y', 'Y', FALSE, FALSE},
        /* 0x16 */ {'u', 'U', FALSE, FALSE},
        /* 0x17 */ {'i', 'I', FALSE, FALSE},
        /* 0x18 */ {'o', 'O', FALSE, FALSE},
        /* 0x19 */ {'p', 'P', FALSE, FALSE},
        /* 0x1A */ {'[', '{', FALSE, FALSE},
        /* 0x1B */ {']', '}', FALSE, FALSE},
        /* 0x1C */ {'\n', '\n', FALSE, FALSE}, // ENTER
        /* 0x1D */ {INV, INV, FALSE, FALSE},   // CTRL_L
        /* 0x1E */ {'a', 'A', FALSE, FALSE},
        /* 0x1F */ {'s', 'S', FALSE, FALSE},
        /* 0x20 */ {'d', 'D', FALSE, FALSE},
        /* 0x21 */ {'f', 'F', FALSE, FALSE},
        /* 0x22 */ {'g', 'G', FALSE, FALSE},
        /* 0x23 */ {'h', 'H', FALSE, FALSE},
        /* 0x24 */ {'j', 'J', FALSE, FALSE},
        /* 0x25 */ {'k', 'K', FALSE, FALSE},
        /* 0x26 */ {'l', 'L', FALSE, FALSE},
        /* 0x27 */ {';', ':', FALSE, FALSE},
        /* 0x28 */ {'\'', '"', FALSE, FALSE},
        /* 0x29 */ {'`', '~', FALSE, FALSE},
        /* 0x2A */ {INV, INV, FALSE, FALSE}, // SHIFT_L
        /* 0x2B */ {'\\', '|', FALSE, FALSE},
        /* 0x2C */ {'z', 'Z', FALSE, FALSE},
        /* 0x2D */ {'x', 'X', FALSE, FALSE},
        /* 0x2E */ {'c', 'C', FALSE, FALSE},
        /* 0x2F */ {'v', 'V', FALSE, FALSE},
        /* 0x30 */ {'b', 'B', FALSE, FALSE},
        /* 0x31 */ {'n', 'N', FALSE, FALSE},
        /* 0x32 */ {'m', 'M', FALSE, FALSE},
        /* 0x33 */ {',', '<', FALSE, FALSE},
        /* 0x34 */ {'.', '>', FALSE, FALSE},
        /* 0x35 */ {'/', '?', FALSE, FALSE},
        /* 0x36 */ {INV, INV, FALSE, FALSE},  // SHIFT_R
        /* 0x37 */ {'*', '*', FALSE, FALSE},  // PAD *
        /* 0x38 */ {INV, INV, FALSE, FALSE},  // ALT_L
        /* 0x39 */ {' ', ' ', FALSE, FALSE},  // SPACE
        /* 0x3A */ {INV, INV, FALSE, FALSE},  // CAPSLOCK
        /* 0x3B */ {INV, INV, FALSE, FALSE},  // F1
        /* 0x3C */ {INV, INV, FALSE, FALSE},  // F2
        /* 0x3D */ {INV, INV, FALSE, FALSE},  // F3
        /* 0x3E */ {INV, INV, FALSE, FALSE},  // F4
        /* 0x3F */ {INV, INV, FALSE, FALSE},  // F5
        /* 0x40 */ {INV, INV, FALSE, FALSE},  // F6
        /* 0x41 */ {INV, INV, FALSE, FALSE},  // F7
        /* 0x42 */ {INV, INV, FALSE, FALSE},  // F8
        /* 0x43 */ {INV, INV, FALSE, FALSE},  // F9
        /* 0x44 */ {INV, INV, FALSE, FALSE},  // F10
        /* 0x45 */ {INV, INV, FALSE, FALSE},  // NUMLOCK
        /* 0x46 */ {INV, INV, FALSE, FALSE},  // SCRLOCK
        /* 0x47 */ {'7', INV, FALSE, FALSE},  // pad 7 - Home
        /* 0x48 */ {'8', INV, FALSE, FALSE},  // pad 8 - Up
        /* 0x49 */ {'9', INV, FALSE, FALSE},  // pad 9 - PageUp
        /* 0x4A */ {'-', '-', FALSE, FALSE},  // pad -
        /* 0x4B */ {'4', INV, FALSE, FALSE},  // pad 4 - Left
        /* 0x4C */ {'5', INV, FALSE, FALSE},  // pad 5
        /* 0x4D */ {'6', INV, FALSE, FALSE},  // pad 6 - Right
        /* 0x4E */ {'+', '+', FALSE, FALSE},  // pad +
        /* 0x4F */ {'1', INV, FALSE, FALSE},  // pad 1 - End
        /* 0x50 */ {'2', INV, FALSE, FALSE},  // pad 2 - Down
        /* 0x51 */ {'3', INV, FALSE, FALSE},  // pad 3 - PageDown
        /* 0x52 */ {'0', INV, FALSE, FALSE},  // pad 0 - Insert
        /* 0x53 */ {'.', 0x7F, FALSE, FALSE}, // pad . - Delete
        /* 0x54 */ {INV, INV, FALSE, FALSE},  //
        /* 0x55 */ {INV, INV, FALSE, FALSE},  //
        /* 0x56 */ {INV, INV, FALSE, FALSE},  //
        /* 0x57 */ {INV, INV, FALSE, FALSE},  // F11
        /* 0x58 */ {INV, INV, FALSE, FALSE},  // F12
        /* 0x59 */ {INV, INV, FALSE, FALSE},  //
        /* 0x5A */ {INV, INV, FALSE, FALSE},  //
        /* 0x5B */ {INV, INV, FALSE, FALSE},  // Left Windows
        /* 0x5C */ {INV, INV, FALSE, FALSE},  // Right Windows
        /* 0x5D */ {INV, INV, FALSE, FALSE},  // Clipboard
        /* 0x5E */ {INV, INV, FALSE, FALSE},  //

        // Print Screen 是强制定义 本身是 0xB7
        /* 0x5F */ {INV, INV, FALSE, FALSE}, // PrintScreen
};

static lock_t lock;
static struct task_t *waiter;

#define BUFFER_SIZE     64
static char buffer[BUFFER_SIZE];
static fifo_t fifo;

static bool capslock_state; // 大写锁定
static bool scrlock_state;  // 滚动锁定
static bool numlock_state;  // 数字锁定
static bool extcode_state;  // 扩展码状态

// CTRL 键状态
#define ctrl_state (keymap[KEY_CTRL_L][2] || keymap[KEY_CTRL_L][3])
// ALT 键状态
#define alt_state (keymap[KEY_ALT_L][2] || keymap[KEY_ALT_L][3])
// SHIFT 键状态
#define shift_state (keymap[KEY_SHIFT_L][2] || keymap[KEY_SHIFT_R][2])

static void keyboard_wait()
{
    u8 state;
    do
    {
        state = inb(KEYBOARD_CTRL_PORT);
    } while (state & 0x02); // 读取键盘缓冲区，直到为空
}

static void keyboard_ack()
{
    u8 state;
    do
    {
        state = inb(KEYBOARD_DATA_PORT);
    } while (state != KEYBOARD_CMD_ACK);
}

static void set_leds()
{
    u8 leds = (capslock_state << 2) | (numlock_state << 1) | scrlock_state;
    keyboard_wait();
    // 设置 LED 命令
    outb(KEYBOARD_DATA_PORT, KEYBOARD_CMD_LED);
    keyboard_ack();

    keyboard_wait();
    // 设置 LED 灯状态
    outb(KEYBOARD_DATA_PORT, leds);
    keyboard_ack();
}

void keyboard_handler(int vector)
{
    assert(vector == 0x21);
    send_eoi(vector);
    u16 scancode = inb(KEYBOARD_DATA_PORT);
    u8 ext = 2;

    if (scancode == 0xE0) {
        // 扩展状态
        extcode_state = TRUE;
        return;
    }

    if (extcode_state) {
        ext = 3;
        scancode |= 0xE000;
        extcode_state = FALSE;
    }

    u16 makecode = (scancode & 0x7F);
    if (makecode == CODE_PRINT_SCREEN_DOWN) {
        makecode = KEY_PRINT_SCREEN;
    }

    if (makecode > KEY_PRINT_SCREEN) {
        return;
    }

    bool breakcode = ((scancode & 0x0080) != 0);
    if (breakcode) {
        keymap[makecode][ext] = FALSE;
        return;
    }

    keymap[makecode][ext] = TRUE;

    bool led = FALSE;
    if (makecode == KEY_NUMLOCK) {
        numlock_state = !numlock_state;
        led = TRUE;
    } else if (makecode == KEY_CAPSLOCK) {
        capslock_state = !capslock_state;
        led = TRUE;
    } else if (makecode == KEY_SCRLOCK) {
        scrlock_state = !scrlock_state;
        led = TRUE;
    }

    if (led) {
        set_leds();
    }
    // 计算shift状态
    bool shift = FALSE;
    if(capslock_state && ('a' <= keymap[makecode][0] && keymap[makecode][0] <= 'z')) {
        shift = !shift;
    }
    if (shift_state) {
        shift = !shift;
    }

    char ch = 0;
    if (ext == 3 && (makecode != KEY_SLASH)) {
        ch = keymap[makecode][1];
    } else {
        ch = keymap[makecode][shift];
    }

    if (ch == INV) {
        return;
    }

    fifo_put(&fifo, ch);
    if (waiter != NULL) {
        task_unblock(waiter);
        waiter = NULL;
    }
}

u32 keyboard_read(void *dev, char *buf, u32 count)
{
    lock_acquire(&lock);
    int nr = 0;
    while (nr < count)
    {
        while (fifo_empty(&fifo))
        {
            waiter = running_task();
            task_block(waiter, NULL, TASK_BLOCKED);
        }
        buf[nr++] = fifo_get(&fifo);
    }
    lock_release(&lock);
    return count;
}

void keyboard_init()
{
    numlock_state = FALSE;
    scrlock_state = FALSE;
    capslock_state = FALSE;
    extcode_state = FALSE;
    fifo_init(&fifo, buffer, BUFFER_SIZE);
    lock_init(&lock);
    waiter = NULL;

    set_leds();
    set_interrupt_handler(IRQ_KEYBOARD, keyboard_handler);
    set_interrupt_mask(IRQ_KEYBOARD, TRUE);

    device_install(
        DEV_CHAR, DEV_KEYBOARD, 
        NULL, "keyboard", 0,
        NULL, keyboard_read, NULL);
}